package com.ymx.darling.executor.mapping.relation;

import com.ymx.darling.bean.entity.BeanInfo;
import com.ymx.darling.bean.entity.FieldInfo;
import com.ymx.darling.bean.sql.Statement;
import com.ymx.darling.exception.EntityException;
import com.ymx.darling.executor.Executor;
import com.ymx.darling.executor.mapping.relation.annotation.RelationOneToOne;
import com.ymx.darling.session.setting.relation.RelationEntity;
import com.ymx.darling.util.ReflectUtil;
import com.ymx.darling.util.StringUtil;
import com.ymx.darling.wrapper.QueryWrapper;
import com.ymx.darling.wrapper.build.SelectBuilder;
import com.ymx.darling.bean.sql.SqlStatement;

import java.util.*;

/**
 * 关联一对一执行器
 * @author 爱Java的小于
 */
public class RelationOneToOneLoader extends RelationLoader{
    private final RelationOneToOne oneToOne;

    public RelationOneToOneLoader(RelationOneToOne oneToOne) {
        this.oneToOne = oneToOne;
    }

    @Override
    public void invoke(Statement statement, RelationEntity relationEntity, List<?> resultList) {
        // 构建目标关联字段的sql
        SqlStatement sqlStatement = this.buildTargetQuerySql(statement.getBeanInfo(), relationEntity.getBeanInfo(), resultList);
        // 构建目标关联子字段的statement
        Statement fieldQueryStatement = this.createStatement(statement, relationEntity, sqlStatement);
        // 执行sql 并将结果集按照提前指定号的字段放入大Map中
        Map<?, ?> resultMap = this.doQuery(fieldQueryStatement);
        // 完成赋值
        this.putValueToResult(statement.getBeanInfo(), relationEntity, resultList, resultMap);
    }


    /**
     * 构建目标表的查询sql
     * @param selfBeanInfo 当前表的实体BeanInfo
     * @param targetBeanInfo 目标表的实体BeanInfo
     * @param resultList 主查询的结果
     * @return sqlStatement
     */
    private SqlStatement buildTargetQuerySql(BeanInfo selfBeanInfo, BeanInfo targetBeanInfo, List<?> resultList) {
        QueryWrapper queryWrapper = this.createTargetTableQueryWrapper(selfBeanInfo, targetBeanInfo, resultList);
        return new SelectBuilder().buildSelect(queryWrapper, targetBeanInfo);
    }


    /**
     * 执行sql并将结果集按照给定字段的值封装为一个Map
     * @param statement statement
     * @return 返回Map集合 key-给定字段的值 value-实体对象
     */
    private Map<?, ?> doQuery(Statement statement) {
        Executor executor = statement.getExecutor();
        List<Object> resultList = executor.queryList(statement);
        Map<Object, Object> resultMap = new HashMap<>(resultList.size());
        BeanInfo beanInfo = statement.getBeanInfo();
        String targetColumn = this.getTargetColumn(beanInfo);
        FieldInfo targetFieldInfo = beanInfo.getSimpleColumnMap().get(targetColumn);
        resultList.forEach(result -> {
            Object resultKey = ReflectUtil.getValue(targetFieldInfo.getField(), result);
            resultMap.put(resultKey, this.getValueWithTargetClass(result));
        });
        return resultMap;
    }


    protected FieldInfo getSelfColumn(BeanInfo beanInfo) {
        return this.getFieldInfo(beanInfo, this.oneToOne.selfField(), String.format("类%s中缺少id字段,当@RelationOneToOne注解的selfField缺省时会将被@Id标注的字段名当做默认名", beanInfo.getModule().getName()));
    }

    @Override
    public Class<?> getTargetClass() {
        return oneToOne.targetClass();
    }

    private FieldInfo getFieldInfo(BeanInfo beanInfo, String selfOrTarget, String idNodeFoundErrMsg) {
        Map<String, FieldInfo> columnInfoMap = beanInfo.getFieldMap();
        if (StringUtil.isNoNull(selfOrTarget)) {
            if (!columnInfoMap.containsKey(selfOrTarget)) {
                throw new EntityException(String.format("字段'%s'在类'%s'中不存在", selfOrTarget, beanInfo.getModule().getName()));
            }
            return columnInfoMap.get(selfOrTarget);
        } else {
            FieldInfo idFieldInfo = beanInfo.getIdColumnInfo();
            if (Objects.isNull(idFieldInfo)) {
                throw new EntityException(idNodeFoundErrMsg);
            }
            return idFieldInfo;
        }
    }


    protected String getTargetColumn(BeanInfo beanInfo) {
        return this.getFieldInfo(beanInfo, this.oneToOne.targetField(), String.format("类%s中缺少id字段,当@RelationOneToOne注解的targetField缺省时会将被@Id标注的字段名当做默认名", beanInfo.getModule().getName())).getColumn();
    }


    protected String[] getSelectColumns() {
        return this.oneToOne.selectColumns();
    }
}
